#ifndef _CG_FILE_H
#define _CG_FILE_H

#include <math.h>
#include <ctime>
#include <iostream>
#include <fstream>

#ifdef WIN32
# include <windows.h>
#endif
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#else
# include <GL/gl.h>
# include <GL/glut.h>
# include <GL/glext.h>
#endif
#include <Cg\cg.h>
#include <Cg\cgGL.h>

#ifndef CWD
# define CWD ""
#endif

using namespace std;

// texture 
typedef struct TEXTURE2D
{
	GLuint id;		// OpenGL texture id
	int width;
	int height;
	int maxcol;		// max. colors
} Texture2D;

static Texture2D g_Texture;		// texture structure

static void CheckCgError(void)
{
  CGerror err = cgGetError();

  if (err != CG_NO_ERROR)
   {
     printf("CG error: %s\n", cgGetErrorString(err));
     exit(1);
   }
}

// -----------------------------------------------
// Load texture from given ppm file
// 
// @param   _filename    - path to file (PPM format)
// @return  true if successful, false otherwise
// With Thanks to Ram Nair for this function
// -----------------------------------------------
static bool load_ppm(char* _filename)
{
    FILE* stream;
    char line[100];

    /* can file be accessed ? */
    if ( (stream = fopen(_filename,"rb")) == NULL )
    {
        printf("ERROR: unable to access file !");
        return false;
    }

    /* file header */
    if ( fscanf( stream,
                "%s ", line ) != 1 || strcmp(line,"P6")!=0 )
    {
        printf("ERROR: file header invalid (must be \'P6\')");
        return false;
    }

	/* texture width */
	if ( fscanf( stream,
				"%d ", &g_Texture.width ) != 1 )
    {
        printf("ERROR: width value invalid !");
        return false;
    }

	/* texture height*/
	if ( fscanf( stream,
				"%d ", &g_Texture.height ) != 1 )
    {
        printf("ERROR: height value invalid !");
        return false;
    }

	/* max. color value */
	if ( fscanf( stream,
				"%d ", &g_Texture.maxcol ) != 1 || g_Texture.maxcol>65535)
    {
        printf("ERROR: max. colors value invalid (must be between 0 and 65535)");
        return false;
    }

    /* read from file */

	// no. of values to read = RGB x texture area
	size_t valuesToRead = g_Texture.width * g_Texture.width * 3;


    // 1 byte per color
	if (g_Texture.maxcol<=255)
	{
        GLubyte* data = (GLubyte*)malloc(sizeof(GLubyte) * valuesToRead);

        // read from file
        if ( fread(data, sizeof(GLubyte),
            valuesToRead, stream) < valuesToRead )
        {
            printf("ERROR: RGB color data invalid");
            return false;
        }

		// build our texture
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, g_Texture.width, g_Texture.height, GL_RGB, GL_UNSIGNED_BYTE, data);

		// free mem
		free(data);
	}
    // 2 bytes per color
	else
	{
        GLushort* data = (GLushort*)malloc(sizeof(GLushort) * valuesToRead);

        // read from file
        if ( fread(data, sizeof(GLushort),
            valuesToRead, stream) < valuesToRead )
        {
            printf("ERROR: RGB color data invalid");
            return false;
        }
		// build our texture
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, g_Texture.width, g_Texture.height, GL_RGB, GL_UNSIGNED_BYTE, data);
		// free mem
		free(data);
	}

	// close file
	fclose(stream);

    return true;
}

// -----------------------------------------------
// Load texture from given 24-bit bitmap file
//
// @param   _filename    - path to file (BMP format)
// @return  true if successful, false otherwise
// With Thanks to Ram Nair for this function
// -----------------------------------------------
static bool load_bmp24(char* _filename)
{
    FILE* stream;
    unsigned short int fileType;
    short int numPlanes, bitsPerPixel;
    long int dataOffset;

    /* can file be accessed ? */
    if ( (stream = fopen(_filename,"rb")) == NULL )
    {
        printf("ERROR: unable to access file !");
        return false;
    }
    /* check that file is a bitmap */
    if(fread(&fileType, sizeof(short int), 1, stream) < 1 || fileType!=19778)
    {
        printf("ERROR: invalid bitmap header!");
        return false;
    }
    /* skip file size and reserved fields of bitmap file header */
    fseek(stream, 8, SEEK_CUR);
    /* get the position of the actual bitmap data */
    if (fread(&dataOffset, sizeof(long int), 1, stream) < 1)
    {
        printf("ERROR: unable to get position of data bits !"); 
        return false;
    }
    /* skip size of bitmap info header */
    fseek(stream, 4, SEEK_CUR);
    /* get the width of the bitmap */
    if (fread(&g_Texture.width, sizeof(int), 1, stream) < 1)
    {
        printf("ERROR: unable to get width !");
        return false;
    }
    /* get the height of the bitmap */
    if ( fread(&g_Texture.height, sizeof(int), 1, stream) < 1 )
    {
        printf("ERROR: unable to get height !");
        return false;
    }

	/* get the number of planes (must be set to 1) */
    if ( fread(&numPlanes, sizeof(short int), 1, stream) < 1 || numPlanes != 1 )
    {
        printf("ERROR: number of planes must be 1 !");
        return false;
    }
    /* get the number of bits per pixel */
    if ( fread(&bitsPerPixel, sizeof(short int), 1, stream) < 1 || bitsPerPixel!=24 )
    {
        printf("ERROR: bits per pixel must be 24 !"); 
        return false;
    }

    /* seek to the actual data */
    fseek(stream, dataOffset, SEEK_SET);

    /* setup mem for data */
    size_t valuesToRead = g_Texture.width * g_Texture.height * 3;
    GLubyte* data = (GLubyte*)malloc(sizeof(GLubyte) * valuesToRead);

    /* read from file */
    if ( fread(data, sizeof(GLubyte),
        valuesToRead, stream) < valuesToRead )
    {
        printf("ERROR: RGB color data invalid");
        return false;
    }

	// build our texture
    // NOTE: bmp color values are in BGR order
	//gluBuild2DMipmaps(GL_TEXTURE_2D, 3, g_Texture.width, g_Texture.height ,GL_BGR_EXT, GL_UNSIGNED_BYTE, data);
	
	/*float *set1 = new float[g_Texture.width*g_Texture.height*3];
	srand((unsigned int)(time(0)));
	for(int i = 0; i < g_Texture.width*g_Texture.height*3; ++ i) {
		set1[i] = float(double(rand()) / double(RAND_MAX));
	}*/
	//glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 3, g_Texture.width, g_Texture.height, 0, GL_BGR_EXT, GL_FLOAT, set1);

	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 3, g_Texture.width, g_Texture.height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, data);

	// free mem
	free(data);

	// close file
	fclose(stream);

    return true;
}

// -----------------------------------------------
// Load texture from given yuv file
//
// @param   _filename    - path to file (yuv format)
// @return  true if successful, false otherwise
// Written by Ben Cope
// -----------------------------------------------
static bool load_yuv(char* _filename)
{
	FILE* stream;

	/* can file be accessed ? */
    if ( (stream = fopen(_filename,"rb")) == NULL )
    {
        printf("ERROR: unable to access file !");
        return false;
    }

	// No file header so must specify size outside

	g_Texture.width = 320;
	g_Texture.height = 240;
	int pixels = g_Texture.width*g_Texture.height;

	// Read in Data
	GLubyte* data_in = (GLubyte*)malloc(sizeof(GLubyte) * pixels*2);
	if ( fread(data_in, sizeof(GLubyte),
        pixels*2, stream) < pixels*2 )
    {
        printf("ERROR: YUV data invalid");
        return false;
    }

	// Convert to float and remove offset from cbcr component
	GLfloat* data_yc = (GLfloat*)malloc(sizeof(GLfloat) * pixels*2);
	
	for (int i=0; i<pixels*2; i+=2)
	{
		data_yc[i] = data_in[i];
		data_yc[i+1] = (data_in[i+1]) - 128;
	}

	free(data_in);

	// convert from 4-2-2 format to std yuv
	GLfloat* data_yuv = (GLfloat*)malloc(sizeof(GLfloat) * pixels*3);
	for (int i=0; i<pixels*2; i+=4)
	{
		data_yuv[(i*3/2)] = data_yc[i];
		data_yuv[(i*3/2)+1] = (data_yc[min(i+1,pixels*2-1)] + data_yc[max(i-3,0)])/2;
		data_yuv[(i*3/2)+2] = (data_yc[min(i+3,pixels*2-1)] + data_yc[max(i-1,0)])/2;

		data_yuv[(i*3/2)+3] = data_yc[i+2];
		data_yuv[(i*3/2)+4] = (data_yc[min(i+1,pixels*2-1)] + data_yc[min(i+5,pixels*2-1)])/2;
		data_yuv[(i*3/2)+5] = (data_yc[min(i+3,pixels*2-1)] + data_yc[max(i-1,0)])/2;
	}

	free(data_yc);
  
	// Convert yuv to rgb
    GLfloat* data_rgb = (GLfloat*)malloc(sizeof(GLfloat) * pixels*3);
	for (int i=0; i<pixels*3; i+=3)
	{
		data_rgb[i] = data_yuv[i] + data_yuv[i+2]*1.576;
        data_rgb[i+1] = data_yuv[i] + data_yuv[i+1]*-0.227 + data_yuv[i+2]*-0.477;
        data_rgb[i+2] = data_yuv[i] + data_yuv[i+1]*1.826;
	}

	free(data_yuv);

	// Turn upside down for graphics card
	GLfloat* data_out = (GLfloat*)malloc(sizeof(GLfloat)*pixels*3);
	for (int i =0; i<g_Texture.width; i++)
	{
		for (int j=0; j<g_Texture.height; j++)
		{
			data_out[3*g_Texture.width*((g_Texture.height-1)-j)+i*3] = data_rgb[3*g_Texture.width*j+i*3]/256;
			data_out[3*g_Texture.width*((g_Texture.height-1)-j)+i*3+1] = data_rgb[3*g_Texture.width*j+i*3+1]/256;
			data_out[3*g_Texture.width*((g_Texture.height-1)-j)+i*3+2] = data_rgb[3*g_Texture.width*j+i*3+2]/256;
		}
	}

	GLfloat* data_y = (GLfloat*)malloc(sizeof(GLfloat)*pixels);

	for (int i=0; i<pixels; i++)
	{
		data_y[i] = data_out[3*i];
	}
	
	free(data_rgb);

	// close file
	fclose(stream);
   
	// Write to texture (swap for 3/1 component analysis
    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 3, g_Texture.width, g_Texture.height, 0, GL_BGR_EXT, GL_FLOAT, data_out);
    //glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 1, g_Texture.width, g_Texture.height, 0, GL_RED, GL_FLOAT, data_y);

	// free mem
	free(data_out);
	free(data_y);

	return true;
}

// -----------------------------------------------
// Get extension part of given filename
//
// @param   _filename
// @return  if successful, extension in lowercase (e.g. bmp) otherwise empty string
// With Thanks to Ram Nair for this function
// -----------------------------------------------
static char* get_file_extension(char* _filename)
{
    char* ret;          // to hold return val
    int numChars = 0;       // length of string
    int lastIndex = 0;  // last index of '.'
    int i;

    // get length of string
    char* ch = _filename;
    while (*ch!=0)
    {
        if (*ch=='.')
            lastIndex = numChars;

        ch++;
        numChars++;
    }

    // long enough ?
    if (numChars>3)
    {
        ret = new char[numChars];
        i = 0;
        ch = _filename+lastIndex+1;
        while (*ch!=0)
        {
            // make everything lowercase
            if ((int)*ch < 97)
                ret[i] = *ch + 32;      // get lowercase equivalent
            else
                ret[i] = *ch;

            i++;
            ch++;
        }
        ret[i] = 0;     // null-character to terminate string
    }


    return ret;
}

// -----------------------------------------------
// Load texture from given file
//
// @param   _filename    - path to file (BMP or PPM format)
// @return  true if successful, false otherwise
// With Thanks to Ram Nair for this function
// -----------------------------------------------
bool load_texture(char* _filename)
{
    char* ext;      // file extension
	bool wrap = true;		// wrap texture

    printf("Loading texture...\t");

	/* set up texture */
	// allocate a texture name
    glGenTextures( 1, &g_Texture.id );
    // select our current texture
    //glBindTexture( GL_TEXTURE_2D, g_Texture.id );
	glBindTexture( GL_TEXTURE_RECTANGLE_ARB, g_Texture.id );
	// byte alignment
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    /* file format */

    ext = get_file_extension(_filename);

    // bitmap
    if (strcmp(ext,"bmp")==0)
    {
        if (!load_bmp24(_filename))
            return false;
    }
    // ppm
    else if(strcmp(ext,"ppm")==0)
    {
        if (!load_ppm(_filename))
            return false;
    }
	// yuv
    else if(strcmp(ext,"yuv")==0)
    {
        if (!load_yuv(_filename))
            return false;
    }
    // unknown
    else
    {
        printf("ERROR: unknown file format");
        return false;
    }

	/*glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);*/

	// wrap texture and setup bilinear filters
    //glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    //glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    //glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	//glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    //glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
	// mix texture with color
  	//glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    cout << "done (" << g_Texture.width << " x " << g_Texture.height << " pixels)\n";
    return true;
}

#endif